#pragma region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU Lesser General Public License.
/// </copyright>
#pragma endregion

#include "stdafx.h"

using namespace SharedMemory;

OSHClient::OSHClient() : _hostGuid(GUID_NULL), _comInitialized(false), _destruction(false)
{
	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Verbose,
		"OSHClient::OSHClient() : 0x%08X\r\n", this);
}

OSHClient::~OSHClient()
{
	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Verbose,
		"OSHClient::~OSHClient() : 0x%08X\r\n", this);
	_destruction = true;
	Disconnect();
}

STDMETHODIMP OSHClient::Connect(BSTR hostGuid)
{
	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::Connect(%S)\r\n", hostGuid);

	//ATLASSERT(0);

	if(IsConnected())
		return E_UNEXPECTED;

	HRESULT hr = S_OK;
	
	if(hostGuid == NULL || ::SysStringLen(hostGuid) == 0)
		_hostGuid = __uuidof(OSHLibrary::IHost);
	else
		hr = CLSIDFromString(hostGuid, &_hostGuid);

	if(FAILED(hr)) return hr;
	if(_hostGuid == GUID_NULL) return E_UNEXPECTED;

	hr = StartMonitoring();
	if(FAILED(hr))
		_hostGuid = GUID_NULL;

	return hr;
}

STDMETHODIMP OSHClient::Disconnect()
{
	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::Disconnect()\r\n");

	StopMonitoring();
	_oshSourceName.Empty();
	_hostGuid = GUID_NULL;
	return S_OK;
}

STDMETHODIMP OSHClient::ActivateHost()
{
	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::ActivateHost()\r\n");

	if(!IsConnected())
		return E_UNEXPECTED;

	CComPtr<OSHLibrary::IHost> oshHost;
	HRESULT hr = GetOSHHost(&oshHost);
	if(FAILED(hr)) return hr;

	hr = oshHost->Activate();

	return hr;
}

bool OSHClient::IsConnected()
{
	return _hostGuid != GUID_NULL && _oshSourceName != NULL;
}

STDMETHODIMP OSHClient::get_State(OSHLibrary::ConnectionStateEnum* state)
{
	if(state == NULL)
		return E_POINTER;
	
	if(IsConnected())
		(*state) = OSHLibrary::oshStateConnected;
	else
		(*state) = OSHLibrary::oshStateDisconnected;
	
	return S_OK;
}

STDMETHODIMP OSHClient::get_SourceName(BSTR* name)
{
	if(!IsConnected())
		return E_UNEXPECTED;

	return _oshSourceName.CopyTo(name);
}

HRESULT OSHClient::StartMonitoring()
{
	if(_monitorThread != NULL)
		return E_UNEXPECTED;

	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::StartMonitoring()\r\n");

	HRESULT hr = S_OK;
	do
	{
		if(!_connectionFirstCheckEvent.Create(NULL, TRUE, FALSE, NULL))
		{
			hr = AtlHresultFromLastError();
			break;
		}

		if(!_monitorThreadStopEvent.Create(NULL, TRUE, FALSE, NULL))
		{
			hr = AtlHresultFromLastError();
			break;
		}

		HRESULT hr = S_OK;
		_monitorThread.Attach(ATL::AtlCreateThread<OSHClient>(&MonitorThreadProc, this));
		if(!_monitorThread)
		{
			hr = AtlHresultFromLastError();
			break;
		}

		//HANDLE handle = _connectionFirstCheckEvent;
		//::MsgWaitForMultipleObjects(1, &handle, TRUE, ConnectionFirstCheckTimeout, QS_ALLEVENTS);

		::WaitForSingleObject(_connectionFirstCheckEvent, ConnectionFirstCheckTimeout);
	}
	while(0);

	if(FAILED(hr))
	{
		_connectionFirstCheckEvent.Close();
		_monitorThreadStopEvent.Close();
	}

	return hr;
}

HRESULT OSHClient::StopMonitoring()
{
	if(!_monitorThread)
		return S_OK;

	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::StopMonitoring() : in\r\n");

	HRESULT hr = S_OK;
	HANDLE handle = _monitorThread;
	if(!_monitorThreadStopEvent.Set() ||
		::WaitForSingleObject(handle, StopMonitoringTimeout) != WAIT_OBJECT_0)
//		::MsgWaitForMultipleObjects(1, &handle, TRUE, StopMonitoringTimeout, QS_ALLEVENTS) != WAIT_OBJECT_0)
	{
		ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Error,
			"OSHClient::StopMonitoring() : killing monitor thread...\r\n");
		
		if(!::TerminateThread(_monitorThread, ERROR_TIMEOUT))
			hr = AtlHresultFromLastError();
	}
	_monitorThread.Close();
	_monitorThreadStopEvent.Close();
	_connectionFirstCheckEvent.Close();

	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::StopMonitoring() : out\r\n");

	return hr;
}

// static 
DWORD WINAPI OSHClient::MonitorThreadProc(OSHClient* self)
{
	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::MonitorThreadProc() : enter\r\n");

	::CoInitializeEx(NULL, COINIT_MULTITHREADED); // COINIT_APARTMENTTHREADED, COINIT_MULTITHREADED

	HRESULT hr = self->CheckConnection();
	self->_connectionFirstCheckEvent.Set();
	
	DWORD alivePingLoops = AlivePingTimeout / HostPingInterval;
	DWORD alivePingLoopCounter = alivePingLoops;
	while(true)
	{
		DWORD result = ::WaitForSingleObject(self->_monitorThreadStopEvent, HostPingInterval);
		if(result == WAIT_OBJECT_0)
			break;
		
		if(result != WAIT_TIMEOUT)
			ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Error,
			"OSHClient::MonitorThreadProc() : WaitError = 0x%08X\r\n", ::GetLastError());

		hr = self->CheckConnection();
		if(FAILED(hr))
		{
			ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Error,
				"OSHClient::MonitorThreadProc() : 0x%08X\r\n", hr);
		}

		if(alivePingLoopCounter-- == 0)
		{
			alivePingLoopCounter = alivePingLoops;
			self->Fire_OnPing();
		}
	}

	self->FireDisconnect();

	::CoUninitialize();

	ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Info,
		"OSHClient::MonitorThreadProc() : exit\r\n");

	return 0;
}

HRESULT OSHClient::CheckConnection()
{
	CComBSTR currentSourceName;
	HRESULT hr = GetOSHSourceName(currentSourceName);
	if(SUCCEEDED(hr))
	{
		if(!IsConnected())
		{
			FireConnect(currentSourceName);
		}
		else
		{
			if(_oshSourceName != currentSourceName)
			{
				FireDisconnect();
				FireConnect(currentSourceName);
			}
		}
	}
	else
	{
		FireDisconnect();
	}

	return hr;
}

HRESULT OSHClient::FireConnect(CComBSTR& sourceName)
{
	_oshSourceName = sourceName;
	return Fire_OnConnect(_oshSourceName);
}

HRESULT OSHClient::FireDisconnect()
{
	if(_oshSourceName != NULL)
	{
		_oshSourceName.Empty();
		if(!_destruction)
			return Fire_OnDisconnect();
	}
	return S_OK;
}

HRESULT OSHClient::GetOSHSourceName(CComBSTR& sourceName)
{
	CComPtr<OSHLibrary::IHost> oshHost;
	HRESULT hr = GetOSHHost(&oshHost);
	if(FAILED(hr)) return hr;

	hr = oshHost->get_SourceName(&sourceName);
	if(FAILED(hr))
	{
		ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Error,
			"OSHClient::GetOSHSourceName() - Can't get active OSH Host's source name: 0x%08X.\r\n", hr);
		return hr;
	}

	return hr;
}

HRESULT OSHClient::GetOSHHost(OSHLibrary::IHost** oshHost)
{
	if(_hostGuid == GUID_NULL) return E_UNEXPECTED;
	if(oshHost == NULL) return E_POINTER;

	CComPtr<IUnknown> oshHostUnknown;
	HRESULT hr = ::GetActiveObject(_hostGuid, NULL, &oshHostUnknown);
	if(FAILED(hr))
	{
		ATLTRACE_TS(atlTraceOSHClient, TraceLevel::Error,
			"OSHClient::GetOSHHost() - Can't get active OSH Host object: 0x%08X.\r\n", hr);
		return hr;
	}

	return oshHostUnknown->QueryInterface(oshHost);
}

